#ifndef _AGORA_PARSER_H
#define _AGORA_PARSER_H

#if defined(_WIN32) || defined(_WIN64)
	#include <Windows.h>

	typedef int socket_len;
#else
	#include <unistd.h>
	#include <stdlib.h>
	#include <string.h>
	#include <pthread.h>
	#include <termios.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <netdb.h>
	
	#define SOCKET_ERROR		0
#endif

#include "globals.h"
#include "Logic.h"
#include "Viewer.h"
#include "opcodes.h"

class Parser;
class Packet;

extern Viewer *viewer;
extern u64 incrementalID;
extern std::map<u64, Parser*> parserList;

#pragma pack(push, 1)
class Packet{
public:
	struct data_{
		u16 size;
		u16 opcode;
		char data[8192];
	};

	u32 count;

public:
	data_ data;

	Packet(){
		memset(&data, 0, sizeof(data_));
		count = 0;
	}

	u8 *getPacket(){
		return (u8*)&data;
	}

	template <typename T>
	void write(T v){
		*(T*)(data.data + count) = v;
		count += sizeof(T);
	}

	template <typename T>
	T read(){
		T r = *(T*)(data.data + count);
		count += sizeof(T);
		return r;
	}

	void writeString(char *string){
		u16 len = strlen(string);
		this->write<u16>(len);

		for(int i = 0; i < len; i++)
			this->write<u8>(string[i]);
	}

	char *readString(){
		int len = this->read<u16>();
		char *name = new char[len+1];

		for(int i = 0; i < len; i++)
			name[i] = this->read<u8>();
		name[len] = 0;

		return name;
	}

	void setSize(){
		this->data.size = count;
	}
};
#pragma pack(pop, 1)

enum parser_status{
	STATUS_DISCONNECTED,
	STATUS_PAUSED,
	STATUS_DOWNLOADING,
	STATUS_UPLOADING,
	STATUS_BOTH
};

class Parser{
private:
	agr_user *user;
	agr_file *file;
	parser_status status;
	Logic *logic;

	SOCKET sock;

public:
	int totalRead;
	int pendingRead;

public:
	Parser(agr_user *user, Logic *logic);

	static Parser *initializeNet(agr_user *user, Logic *logic){
#if defined(_WIN32) || defined(_WIN64)
		WSADATA m_wsaData;
		WSAStartup(MAKEWORD(2,2), &m_wsaData);
#endif

		SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
		sockaddr_in m_siServerInfo;

		m_siServerInfo.sin_family = AF_INET;
		m_siServerInfo.sin_addr.s_addr = INADDR_ANY; 
		m_siServerInfo.sin_port = htons(2500);

		if(bind(s, (sockaddr*)&m_siServerInfo, sizeof(struct sockaddr)) != SOCKET_ERROR)
		{
			viewer->print("NET initialized\n");

			user->socket = s;

			Parser *parser = new Parser(user, logic);
			parser->setStatus(STATUS_DOWNLOADING);

			#if defined(_WIN32) || defined(_WIN64)
				CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Parser::acceptUser, parser, 0, 0);
			#else			
				pthread_create(NULL , NULL, Parser::acceptUser, (void*)parser);
			#endif

			return parser;
		}

		return NULL;
	}

	static Parser *initializeConnection(agr_user *user, Logic *logic){
		SOCKET m_sConnection;
		struct sockaddr_in m_siSin;
		struct hostent* m_hHost;
 
		m_sConnection = socket(AF_INET, SOCK_STREAM, 0);
 
		m_hHost = gethostbyname(agr_utils::IPv4toc(user->ip));
 
		memset( &m_siSin, 0, sizeof m_siSin );
 
		m_siSin.sin_family = AF_INET;
		m_siSin.sin_addr = *(struct in_addr *)m_hHost->h_addr;
		m_siSin.sin_port = htons(2500);
 
		if ( !connect(m_sConnection, (sockaddr*)&m_siSin, sizeof(m_siSin)))
		{
			user->ID = ++incrementalID;
			user->socket = m_sConnection;

			Parser *parser = new Parser(user, logic);
			parser->setStatus(STATUS_DOWNLOADING);

			parserList.insert(std::pair<u64, Parser*>(user->ID, parser));

			Packet packet;
			memset(&(packet.data), 0, sizeof(Packet::data_));
			packet.data.opcode = WELCOME;

			#if defined(_WIN32) || defined(_WIN64)
				CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Parser::socketRecv, parser, 0, 0);
			#else			
				pthread_create(NULL , NULL, Parser::socketRecv, (void*)parser);
			#endif
			
			
			send(user->socket, (const char*)packet.getPacket(), packet.data.size + sizeof(u32), 0);

			return parser;
		}

		return NULL;
	}

	static void acceptUser(Parser *parser){
		viewer->print("Accepting clients\n");

		while(parser->getStatus() != STATUS_DISCONNECTED){
			if(listen(parser->getSocket(), SOMAXCONN ) == SOCKET_ERROR)
				continue;

			SOCKET m_sClient;
			int m_iLength = sizeof(sockaddr_in);

			sockaddr_in m_siServerInfo;

			m_siServerInfo.sin_family = AF_INET;
			m_siServerInfo.sin_addr.s_addr = INADDR_ANY; 
			m_siServerInfo.sin_port = htons(2500);

			m_sClient = accept(parser->getSocket(), (sockaddr*)&m_siServerInfo, (socket_len*)&m_iLength );

			if(m_sClient != SOCKET_ERROR){
				agr_user *user = new agr_user();
				memset(user, 0, sizeof(agr_user));
				//user->ip = agr_utils::getUserIP();
				user->ID = ++incrementalID;
				user->socket = m_sClient;
				
				viewer->print("Client accepted %d\n", user->ID);

				Parser *uParser = new Parser(user, parser->getLogic());
				uParser->setStatus(STATUS_DOWNLOADING);

				parserList.insert(std::pair<u64, Parser*>(user->ID, uParser));

				#if defined(_WIN32) || defined(_WIN64)
					CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Parser::socketRecv, uParser, 0, 0);
				#else			
					pthread_create(NULL , NULL, Parser::socketRecv, (void*)uParser);
				#endif
			}
		}

		delete parser;
	}

	static void socketRecv(Parser *parser){
		u8 *data = new u8[8192];
		memset(data, 0, 8192);
	
		Packet packet;

		while(parser->getStatus() != STATUS_DISCONNECTED){
	/*		int rLen = (parser->totalRead - parser->pendingRead);
			if(rLen == 0)
			{
				rLen = sizeof(u32);
			}
	
			viewer->print("READ %d\n", rLen);
			int bRecv = recv(parser->getSocket(), (char*)(data + (parser->totalRead - parser->pendingRead)), rLen, 0);
	*/

			int bRecv = recv(parser->getSocket(), (char*)data, 8192, 0);
			packet.data = *(Packet::data_*)data;
			packet.count = 0;

			viewer->print("Packet (%X): %x %x (%d)\n", parser, packet.data.size, packet.data.opcode, bRecv);
			if(bRecv >= sizeof(u32)){ //sizeof(u32) = sizeof(packet_header)
				if(!parser->processPacket(&packet, bRecv)){
					viewer->print("Error parsing packet: %X\n", packet.data.opcode);		
				}
			}

			memset(data, 0, 8192);
		}

		parser->getLogic()->removeUser(parser->getUser());
		delete parser;
	}

	void setStatus(parser_status status);
	agr_file *getFile();
	agr_user *getUser();
	Logic *getLogic();
	SOCKET getSocket();
	parser_status getStatus();

	bool processPacket(Packet *packet, int bRecv);

	bool welcomeUser();
	bool addUser();
	bool checkDisconnect();

	bool retrieveFiles(u64 arg0 = 0);
	bool getFilesList(Packet *packet);

	// ----- VIEWER RELATED ----- //
#ifdef VIEWER_CONSOLE_MODE
	bool requestUserID(u64 arg0 = 0);
#endif
};

#endif
